<?php

/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */

namespace common\components\wechat\models;

use Yii;
use yii\helpers\VarDumper;
use yii\helpers\Inflector;
use common\components\wechat\lib\WxPayConfig;
use common\components\wechat\lib\WxPayException;

/**
 * 数据对象基础类，该类中定义数据类最基本的行为，包括：
 * 计算/设置/获取签名、输出xml格式的参数、从xml读取数据对象等
 *
 * @author emhome
 */
class BaseModel extends \yii\base\Model {

    const WECHAT_PAYMENT_URL = 'https://api.mch.weixin.qq.com';
    const WECHAT_PAYMENT_NOTIFY_URL = 'https://api.mch.weixin.qq.com';
    const UNIFIED_ORDER_URL = '/pay/unifiedorder';

    /**
     * 支付方式
     */
    const TRADE_TYPE_JSAPI = 'JSAPI';
    const TRADE_TYPE_NATIVE = 'NATIVE';
    const TRADE_TYPE_APP = 'APP';
    const TRADE_TYPE_MWEB = 'MWEB';

    //
    private $_model = null;

    public function __construct($config = array()) {
        parent::__construct($config);
        if ($this->model == null) {
            throw new \yii\base\InvalidParamException('缺少必要参数，支付发起失败！');
        }
    }

    public function setModel($model) {
        $this->_model = $model;
    }

    public function getModel() {
        return $this->_model;
    }

    /**
     * 输出xml字符
     * @throws WxPayException
     */
    public function toXml() {
        if (!is_array($this->attributes) || count($this->attributes) <= 0) {
            throw new WxPayException("数组数据异常！");
        }

        $xml = "<xml>";
        foreach ($this->attributes as $key => $val) {
            if (is_numeric($val)) {
                $xml .= "<" . $key . ">" . $val . "</" . $key . ">";
            } else {
                $xml .= "<" . $key . "><![CDATA[" . $val . "]]></" . $key . ">";
            }
        }
        $xml .= "</xml>";
        return $xml;
    }

    /**
     * 将xml转为array
     * @param string $xml
     * @throws WxPayException
     */
    public function fromXml($xml) {
        if (!$xml) {
            throw new WxPayException("xml数据异常！");
        }
        //将XML转为array 
        return json_decode(json_encode(simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA)), true);
    }

    /**
     * 格式化参数格式化成url参数
     */
    public function formatQueryParams($params) {
        $buff = "";
        foreach ($params as $k => $v) {
            if ($k != "sign" && $v != "" && !is_array($v)) {
                $buff .= $k . "=" . $v . "&";
            }
        }
        return trim($buff, "&");
    }

    /**
     * 生成签名串
     * @return 签名，本函数不覆盖sign成员变量，如要设置签名需要调用setSign方法赋值
     */
    public function generateSign(&$sign) {
        $attributes = $this->attributes;
//        unset($attributes['model']);
        //签名步骤一：按字典序排序参数
        ksort($attributes);
        $string = $this->formatQueryParams($attributes);
        //签名步骤二：在string后加入KEY
        //签名步骤三：MD5加密
        //签名步骤四：所有字符转为大写
        return $sign = strtoupper(md5($string . "&key=" . $this->model->paymentConfig['key']));
    }

    /**
     * 检测签名
     */
    public function checkSign() {
        if (!$this->sign) {
            return true;
        }
        $sign = $this->generateSign();
        if ($this->sign == $sign) {
            return true;
        }
        throw new \yii\db\Exception("签名错误！");
    }

    /**
     * 以post方式提交xml到对应的接口url
     * 
     * @param string $xml  需要post的xml数据
     * @param string $url  url
     * @param bool $useCert 是否需要证书，默认不需要
     * @param int $second   url执行超时时间，默认30s
     * @throws WxPayException
     */
    public static function postHttp($xml, $url, $useCert = false, $second = 30) {
        $ch = curl_init();
        //设置超时
        curl_setopt($ch, CURLOPT_TIMEOUT, $second);

        //如果有配置代理这里就设置代理
        if (WxPayConfig::CURL_PROXY_HOST != "0.0.0.0" && WxPayConfig::CURL_PROXY_PORT != 0) {
            curl_setopt($ch, CURLOPT_PROXY, WxPayConfig::CURL_PROXY_HOST);
            curl_setopt($ch, CURLOPT_PROXYPORT, WxPayConfig::CURL_PROXY_PORT);
        }
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE);
        //设置header
        curl_setopt($ch, CURLOPT_HEADER, FALSE);
        //要求结果为字符串且输出到屏幕上
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);

        if ($useCert == true) {
            //设置证书
            //使用证书：cert 与 key 分别属于两个.pem文件
            curl_setopt($ch, CURLOPT_SSLCERTTYPE, 'PEM');
            curl_setopt($ch, CURLOPT_SSLCERT, WxPayConfig::SSLCERT_PATH);
            curl_setopt($ch, CURLOPT_SSLKEYTYPE, 'PEM');
            curl_setopt($ch, CURLOPT_SSLKEY, WxPayConfig::SSLKEY_PATH);
        }
        //post提交方式
        curl_setopt($ch, CURLOPT_POST, TRUE);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $xml);
        //运行curl
        $data = curl_exec($ch);
        //返回结果
        if ($data) {
            curl_close($ch);
            return $data;
        } else {
            $error = curl_errno($ch);
            curl_close($ch);
            throw new WxPayException("curl出错，错误码:$error");
        }
    }

    /**
     * 获取毫秒级别的时间戳
     */
    public static function getMillisecond() {
        //获取毫秒的时间戳
        $time = explode(" ", microtime());
        $time = $time[1] . ($time[0] * 1000);
        $time2 = explode(".", $time);
        $time = $time2[0];
        return $time;
    }

    public function responseData($xml) {
        return $this->fromXml($xml);
    }

    public static function generateRandom() {
        return md5(rand(0, 1000) + microtime());
    }

    /**
     * 记录处理日志
     */
    public static function invokeLog($prefix, $content) {
        $message = Inflector::id2camel($prefix) . ":";
        $message .= "\n" . VarDumper::export($content);
        Yii::info("\n" . $message, 'payment');
    }

}
